package org.yagnus.langutils;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import org.yagnus.exceptions.DisallowedOperationException;
import org.yagnus.interfaces.Freezable;

/**
 * 
 * @author hc.busy
 * 
 *         This map implements the map interface by adding several methods for
 *         dealing with the accesses to the map asking for a key that is not in
 *         the map.
 * 
 *         This implementation is not thread-safe.
 * 
 */
public class BasicAccessibleMap<K, V> extends BaseAccessibleMap<K, V> implements
		AccessibleMap<K, V>, Freezable {

	final private Map<K, V> backing;// the actual map backing this map

	// Standard map declarations
	/**
	 * This is the default allocate uses HashMap as backing.
	 */
	public BasicAccessibleMap() {
		backing = new HashMap<K, V>();
	}

	/**
	 * uses default map backing but sets default value to the specfied value
	 * 
	 * @param dv
	 */
	public BasicAccessibleMap(V dv) {
		backing = new HashMap<K, V>();
		defaultValue = dv;
	}

	/**
	 * This is constructor requries to specify the map backing the quickie and a
	 * default item.
	 */
	public BasicAccessibleMap(Map<K, V> m, V dv) {
		backing = m;
		defaultValue = dv;
	}

	public final void clear() {
		backing.clear();
	}

	public final boolean containsKey(final Object key) {
		return backing.containsKey(key);
	}

	public final boolean containsValue(final Object value) {
		return backing.containsValue(value);
	}

	public final boolean containsValueWithDefault(final V value) {
		return containsValue(value)
				|| (defaultValue == null || value == null)
				|| (defaultValue != null && value != null && defaultValue
						.equals(value));
	}

	public final Set<Entry<K, V>> entrySet() {
		return backing.entrySet();
	}

	public final V get(final Object key) {
		return backing.get(key);
	}

	public final boolean isEmpty() {
		return backing.isEmpty();
	}

	public final Set<K> keySet() {
		return backing.keySet();
	}

	public final V put(final K key, final V value) {
		if (!frozen) {
			return wrapDefault(backing.put(key, value));
		} else {
			throw new DisallowedOperationException();
		}
	}

	public final V putWithDefault(final K key, final V value) {
		if (!frozen) {
			return wrapDefault(backing.put(key, value));
		} else {
			throw new DisallowedOperationException();
		}
	}

	public final void putAll(final Map<? extends K, ? extends V> t) {
		if (!frozen) {
			backing.putAll(t);
		} else {
			throw new DisallowedOperationException();
		}
	}

	public final V remove(final Object key) {
		if (!frozen) {
			return wrapDefault(backing.remove(key));
		} else {
			throw new DisallowedOperationException();
		}
	}

	public final V removeWithDefault(final Object key) {
		if (!frozen) {
			return wrapDefault(backing.remove(key));
		} else {
			throw new DisallowedOperationException();
		}
	}

	public final int size() {
		return backing.size();
	}

	public final Collection<V> values() {
		return backing.values();
	}

	V defaultValue;

	// Quickie declarations
	/**
	 * Sets a default vaule that is retrieved when map normally returns null due
	 * to missing entry
	 * 
	 * @param d
	 */
	public final void setDefault(final V d) {
		if (frozen) {
			throw new DisallowedOperationException();
		} else {
			defaultValue = d;
		}
	}

	/**
	 * 
	 * Get the current default value.
	 * 
	 * @param d
	 */
	public final V getDefault() {
		return defaultValue;
	}

	/**
	 * Try to get the entry mapped to key k, if not found return default value.s
	 * 
	 * @param k
	 * @return
	 */
	public final V getWithDefault(final K k) {
		return wrapDefault(backing.get(k));
	}

	/**
	 * tries to get the key, if it is not mapped, return v instead. but do not
	 * update the map.
	 * 
	 * @param k
	 *            The key to find
	 * @param v
	 *            The value to return in case key is not found.
	 * @return what k was mapped to or v is k was nt found.
	 */
	public final V getWithDefault(final K k, final V v) {
		return wrapDefault(backing.get(k), v);
	}

	/**
	 * This method is copied from python syntax. returns the value mapped to k
	 * in the map if k was mapped; otherwise map k to v, and return v.
	 * 
	 * @param k
	 *            the key to find
	 * @param v
	 *            the value to map k to (and return) if k was not mapped.
	 * @return the value k is mapped to at end of the
	 */
	public final V getSetToDefault(final K k, final V v) {
		V ret = backing.get(k);
		if (ret == null) {
			backing.put(k, v);
			ret = v;
		}
		return ret;
	}

	/**
	 * override this method to provide a way to allocate a new default item
	 * lazily.
	 * 
	 * @return the allocate item.
	 */
	protected V allocate() {
		return null;
	}

	/**
	 * Override this method to provide an allocate that allocates based on the
	 * key
	 * 
	 * @param k
	 * @return
	 */
	protected V allocate(K k) {
		return allocate();
	}

	/**
	 * Allocate a new one if k is not found
	 * 
	 * @param k
	 * @return
	 */
	public final V getAllocate(final K k) {
		V ret = backing.get(k);
		if (ret == null) {
			if (frozen) {
				throw new DisallowedOperationException();
			}
			ret = allocate(k);
			backing.put(k, ret);
		}
		return ret;
	}

	boolean frozen = false;

	public boolean freeze() {
		frozen = true;
		return true;
	}

	public boolean unfreeze() {
		frozen = false;
		return true;
	}

	public boolean isFrozen() {
		return frozen;
	}
}
